<?php

namespace Moonpie\Plugin\Enhancer\Service\Provider;

use app\common\service\base\Collection;
use app\common\service\container\BaseServiceContainer;
use app\common\service\container\ServiceProviderInterface;
use app\common\service\helper\ArrayHelper;
use app\common\service\helper\ReplaceArrayValue;
use app\common\service\ServiceContainer;
use Doctrine\Common\Cache\ApcCache;
use Doctrine\Common\Cache\ApcuCache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\MemcacheCache;
use Doctrine\Common\Cache\MemcachedCache;
use Doctrine\Common\Cache\RedisCache;
use Doctrine\DBAL\Configuration as DBALConfig;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\Configuration as ORMConfig;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
use Moonpie\Plugin\Enhancer\Service\Doctrine\ManagerRegistry;
use Moonpie\Plugin\Enhancer\Service\View\Driver\TwigEngine;
use think\Config;
use Twig\Environment;
use Twig\Loader\ChainLoader;
use Twig\Loader\FilesystemLoader;

class ServiceProvider implements ServiceProviderInterface
{
    public function register(BaseServiceContainer $container)
    {
        //首先注册twig
        $container->singleton('twig.loader_delegate', function ($c) {
            $loader = new ChainLoader();
            $services = $c->tagged('twig.loader');
            foreach ($services as $service_name => $params) {
                list($service, $attributes) = $params;
                $loader->addLoader($service);
            }

            return $loader;
        });
        $container->singleton('twig.loader_filesystem', function ($c) {
            $paths = [];
            $loader = new FilesystemLoader($paths);

            return $loader;
        });
        $container->tag('twig.loader_filesystem', 'twig.loader', []);
        $container->singleton('twig.environment', function ($c) {
            $options = [];
            $env = new Environment($c->get('twig.loader_delegate'), $options);
            //添加扩展
            $services = $c->tagged('twig.extension');
            foreach ($services as $service_name => $params) {
                list($service, $attributes) = $params;
                $env->addExtension($service);
            }

            return $env;
        });
        $container->singleton('view.twig', function ($c) {
            $config = [];
            $engine = new TwigEngine($c->get('twig.environment'), $config);

            return $engine;
        });
        //接下来是ORM部分
        $this->prepareORMConfig($container);
        $this->prepareORMConnections($container);
        $this->prepareORMManagers($container);
        $container->singleton('doctrine', function ($c) {
            $connections = $c->get('doctrine.config')->get('connections');
            $default_connection = $c->get('doctrine.config')->get('dbal.default_connection');
            $managers = $c->get('doctrine.config')->get('entity_managers');
            $default_manager = $c->get('doctrine.config')->get('orm.default_entity_manager');
            $registry = new ManagerRegistry($c, 'Moonpie Doctrine', $connections, $managers, $default_connection, $default_manager);

            return $registry;
        });
    }

    protected function prepareORMConnections(ServiceContainer $container)
    {
        //现在配置准备好了，现在可以声明配置类信息
        $connections = $container->get('doctrine.config')->get('dbal.connections', []);
        $service_map = [];
        foreach ($connections as $connection_name => $connection_params) {
            $connection_config_service_name = sprintf('doctrine.dbal.connection_config_%s', $connection_name);
            $container[$connection_config_service_name] = function ($c) {
                $dbal_config = new DBALConfig();
                //后期这里完善信息
                return $dbal_config;
            };
            $connection_service_name = sprintf('doctrine.dbal.connection_%s', $connection_name);
            $container->singleton($connection_service_name, function ($c) use ($connection_params, $connection_config_service_name) {
                $dbal_config = $c[$connection_config_service_name];

                return DriverManager::getConnection($connection_params, $dbal_config);
            });
            $service_map[$connection_name] = $connection_service_name;
        }
        $container->get('doctrine.config')->set('connections', $service_map);
    }

    protected function registerDoctrineCacheService($scope, ServiceContainer $container, $serviceName, Collection $config)
    {
        if (!isset($container[$serviceName])) {
            $container->singleton($serviceName, function ($c) use ($config, $scope) {
                $default_config = [
                    'type' => 'array',
                ];
                if (!$c['app_env']['debug']) {
                    if (extension_loaded('apc')) {
                        $default_config = [
                            'type' => 'apc',
                        ];
                    } elseif (extension_loaded('apcu')) {
                        $default_config = [
                            'type' => 'apcu',
                        ];
                    }
                }
                $option = $config->get($scope, $default_config);
                switch ($option['type']) {
                    case 'service':
                        return $c[$option['id']];
                    case 'apc':
                        if (extension_loaded('apc')) {
                            return new ApcCache();
                        } else {
                            throw new \InvalidArgumentException(sprintf('Cache Driver %s  Require Extension: %s', 'Apc', 'apc'));
                        }
                        // no break
                    case 'apcu':
                        if (extension_loaded('apcu')) {
                            return new ApcuCache();
                        } else {
                            throw new \InvalidArgumentException(sprintf('Cache Driver %s  Require Extension: %s', 'Apcu', 'apcu'));
                        }
                        // no break
                    case 'redis':
                        if (extension_loaded('redis')) {
                            $redis = new \Redis();
                            $redis->connect($option['host'], $option['port'], $option['timeout']);
                            if (isset($option['password'])) {
                                $redis->auth($option['password']);
                            }
                            $redis->select($option['db']);
                            $cache = new RedisCache();
                            $cache->setRedis($redis);

                            return $cache;
                        } else {
                            throw new \InvalidArgumentException(sprintf('Cache Driver %s  Require Extension: %s', 'Redis', 'redis'));
                        }
                        // no break
                    case 'memcache':
                        if (extension_loaded('memcache')) {
                            $memcache = new \Memcache();
                            $memcache->connect($option['host'], $option['port'], $option['timeout']);
                            $cache = new MemcacheCache();
                            $cache->setMemcache($memcache);

                            return $cache;
                        } else {
                            throw new \InvalidArgumentException(sprintf('Cache Driver %s  Require Extension: %s', 'Memcache', 'memcache'));
                        }
                        // no break
                    case 'memcached':
                        if (extension_loaded('memcached')) {
                            $memcached = new \Memcached();
                            $memcached->addServer($option['host'], $option['port'], $option['timeout']);
                            $cache = new MemcachedCache();
                            $cache->setMemcached($memcached);

                            return $cache;
                        } else {
                            throw new \InvalidArgumentException(sprintf('Cache Driver %s  Require Extension: %s', 'Memcache', 'memcache'));
                        }
                        // no break
                    case 'array':
                    default:
                        return new ArrayCache();
                }
            });
        }
    }

    protected function prepareORMManagers(ServiceContainer $container)
    {
        //现在声明实体管理器信息
        $doctrine_config = $container->get('doctrine.config');
        $entity_managers = $doctrine_config->get('orm.entity_managers', []);
        $service_map = [];
        foreach ($entity_managers as $entity_manager_name => $manager_config) {
            $name = sprintf('doctrine.orm.entity_manager_%s', $entity_manager_name);
            $manager_option = Collection::make($manager_config);
            //加载结果缓存类
            $result_cache_service_name = sprintf('doctrine.orm.entity_manager.result_cache_%s', $entity_manager_name);
            $this->registerDoctrineCacheService('result_cache_driver', $container, $result_cache_service_name, $manager_option);
            //加载查询缓存类
            $query_cache_service_name = sprintf('doctrine.orm.entity_manager.query_cache_%s', $entity_manager_name);
            $this->registerDoctrineCacheService('query_cache_driver', $container, $query_cache_service_name, $manager_option);
            //加载元数据类
            $metadata_driver_service_name = sprintf('doctrine.orm.entity_manager.metadata_driver_%s', $entity_manager_name);
            $container->singleton($metadata_driver_service_name, function ($c) {
                $chain = new MappingDriverChain();

                return $chain;
            });
            //设置元数据缓存信息
            $metadata_driver_cache_service_name = sprintf('doctrine.orm.entity_manager.metadata_driver_cache_%s', $entity_manager_name);
            $this->registerDoctrineCacheService('metadata_cache_driver', $container, $metadata_driver_cache_service_name, $manager_option);
            $container->singleton($name, function ($c) use (
                $name, $manager_config, $doctrine_config, $entity_manager_name, $result_cache_service_name,
                $query_cache_service_name, $metadata_driver_service_name, $metadata_driver_cache_service_name,
                $manager_option
            ) {
                $config = new ORMConfig();
                //加载结果缓存类
                $result_cache = $c[$result_cache_service_name];
                $config->setResultCacheImpl($result_cache);
                //加载查询缓存类
                $query_cache = $c[$query_cache_service_name];
                $config->setQueryCacheImpl($query_cache);
                //加载元数据类
                /** @var MappingDriverChain $metadata_driver */
                $metadata_driver = $c[$metadata_driver_service_name];
                $config->setMetadataDriverImpl($metadata_driver);
                //设置元数据缓存信息
                $metadata_driver_cache = $c[$metadata_driver_cache_service_name];
                $config->setMetadataCacheImpl($metadata_driver_cache);
                //接下来就可以按照具体的选项处理了
                $item_config = Collection::make($manager_config);
                $mappings = $item_config->get('mappings', []);
                foreach ($mappings as $bundle_name => $options) {
                    $option_collect = Collection::make($options);
                    if ($option_collect->get('mapping', false)) {
                        $entity_namespace = $option_collect->get('prefix');
                        $config->addEntityNamespace($option_collect->get('alias', $bundle_name), $entity_namespace);
                        $target_driver = $this->createDoctrineMetadataDriver($c, $option_collect, $entity_manager_name, $bundle_name);
                        $metadata_driver->addDriver($target_driver, $entity_namespace);
                    }
                }

                //这些使用相同的配置
                $cmf = $manager_option->get('class_metadata_factory_name', 'Doctrine\ORM\Mapping\ClassMetadataFactory');
                $config->setClassMetadataFactoryName($cmf);
                $default_repo_class_name = $manager_option->get('default_repository_class', 'Doctrine\ORM\EntityRepository');
                $config->setDefaultRepositoryClassName($default_repo_class_name);
                $proxy_dir = $doctrine_config->get('orm.proxy_dir');
                $proxy_namespace = $doctrine_config->get('orm.proxy_namespace');
                $config->setProxyDir($proxy_dir);
                $config->setProxyNamespace($proxy_namespace);
                $connection_name = $manager_option->get('connection', 'default');
                $connection_service_name = sprintf('doctrine.dbal.connection_%s', $connection_name);
                /** @var Connection $connection */
                $connection = $c[$connection_service_name];
                $config->setSQLLogger($connection->getConfiguration()->getSQLLogger());
                $entity_manager = EntityManager::create($connection, $config);

                return $entity_manager;
            });
            $service_map[$entity_manager_name] = $name;
        }
        $doctrine_config->set('entity_managers', $service_map);
    }

    /**
     * 根据配置生成合适的元数据驱动.
     *
     * @param $entityEntityName
     * @param $bundleName
     *
     * @return mixed
     */
    protected function createDoctrineMetadataDriver(ServiceContainer $container, Collection $collection, $entityEntityName, $bundleName)
    {
        $service_name = sprintf('doctrine.orm.entity_manager_%s_mapping_%s_driver', $entityEntityName, $bundleName);

        if ($container->has($service_name)) {
            return $container->get($service_name);
        }
        $type = $collection->get('type', 'php');
        switch ($type) {
            case 'annotation':
                $reader_service_name = 'doctrine.orm.metadata.annotation_reader';
                $driver = new AnnotationDriver($container[$reader_service_name], $collection->get('dir'));
                break;
            case 'yml':
            case 'yaml':
                $driver = new YamlDriver($collection->get('dir'));
                break;
            case 'staticphp':
            case 'php':
                $driver = new StaticPHPDriver($collection->get('dir'));
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unsupported Metadata Driver Type: %s', $type));
        }

        return $driver;
    }

    /**
     * 准备好需要的配置信息.
     */
    protected function prepareORMConfig(ServiceContainer $container)
    {
        $container->singleton('doctrine.config', function ($c) {
            $origin = Collection::make(config_get('enhancer', [], 'plugin'));
            //确保默认配置链接信息
            $dbal = [];
            $dbal['default_connection'] = $origin->get('dbal.default_connection', 'default');
            $dbal['types'] = $origin->get('dbal.types', []);
            $dbal['connections'] = $origin->get('dbal.connections', []);
            /*if (empty($dbal['connections'][$dbal['default_connection']])) {
                $dbal['connections'][$dbal['default_connection']] = $this->convertDatabaseConnectionSchemaFromTP5();
            }*/
            $convert_data = $this->convertDatabaseConnectionSchemaFromTP5();
            $dbal['connections'] = ArrayHelper::merge($dbal['connections'], [
                $dbal['default_connection'] => new ReplaceArrayValue($convert_data),
            ]);
            $origin->set('dbal', $dbal);
            //配置连接对象
            $orm = [];
            //现在暂时只用保证有实例管理器就可以
            $orm['default_entity_manager'] = $origin->get('orm.default_entity_manager', 'default');
            $orm['auto_generate_proxy_classes'] = $origin->get('orm.auto_generate_proxy_classes', false);
            $orm['proxy_dir'] = $origin->get('orm.proxy_dir', RUNTIME_PATH.'doctrine/orm/Proxies');
            $orm['proxy_namespace'] = $origin->get('orm.proxy_namespace', 'Proxies');

            //创建初始化管理器
            if (empty($orm['entity_managers'][$orm['default_entity_manager']])) {
                $orm['entity_managers'][$orm['default_entity_manager']] = [
                    'connection' => $dbal['default_connection'],
                ];
            }

            $origin->set('orm', $orm);

            return $origin;
        });
    }

    /**
     * 格式化TP5的数据库连接信息.
     *
     * @return array
     */
    protected function convertDatabaseConnectionSchemaFromTP5()
    {
        static $map = [
            'type' => 'driver', 'dsn' => 'url', 'username' => 'user',
            'hostname' => 'host', 'hostport' => 'port', 'database' => 'dbname',
            'password' => 'password',
        ];
        $config = Collection::make(Config::get('database'));
        $final = [];
        foreach ($map as $tp_field => $doctrine_field) {
            if (isset($config[$tp_field])) {
                $tp_trans_data = in_array($config[$tp_field], ['mysql', 'sqlite', 'pgsql']) ? 'pdo_'.$config[$tp_field] : $config[$tp_field];
                $final[$doctrine_field] = $tp_trans_data;
            }
        }

        return $final;
    }
}
